﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Skugo.Shared;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Skugo
{
    public class TreeItemEvent : EventArgs
    {
        public TreeNode Node;
        public Int32 ItemIndex;
    }

    public class TreeItemMoveEvent : EventArgs
    {
        public TreeNode Node;
        public Int32 FromIndex;
        public Int32 ToIndex;
    }

    /// <summary>
    /// A callback delegate for context commands.
    /// </summary>
    public delegate void ContextCommandFn<T>(T value);


    public class ContextCommandSetup<T>
    {
        /// <summary>
        /// Construct a command with a given name.
        /// </summary>
        /// <param name="name">The name of the command, which is required.</param>
        public ContextCommandSetup(String name)
        {
            this.Name = name;
        }

        /// <summary>
        /// The name of the command (a required parameter).
        /// </summary>
        public String Name;

        /// <summary>
        /// The description of the command (used for tool-tips and extra information).
        /// </summary>
        public String Description = String.Empty;

        /// <summary>
        /// An optional icon that we use for the command (displayed in menus, toolbars, etc).
        /// </summary>
        public Icon Icon;

        /// <summary>
        /// The key that we press to invoke the shortcut. To not have a shortcut key, leave it as None.
        /// </summary>
        public Key ShortcutKey;

        /// <summary>
        /// Any keys that must be pressed with the shortcut key (such as control, alt, etc).
        /// To not have a shortcut key modifier, leave it as None.
        /// </summary>
        public KeyModifiers ShortcutModifiers;

        /// <summary>
        /// Anyone who wants to listen to the command when it occurs.
        /// </summary>
        public ContextCommandFn<T> Handlers;
    }
    
    public class Tree : DockablePlugin
    {
        /// <summary>
        /// The type of message we send or receieve.
        /// </summary>
        enum MessageType : byte
        {
            TreeSnapshot,
            ItemSelected,
            ItemDoubleClicked,
            ItemMoved,
            ItemRenamed,
            SubTreeExpanded,
            AddContextMenuItem,
            ContextCommandExecuted,
        }

        public event EventHandler<TreeItemEvent> OnItemSelected;
        public event EventHandler<TreeItemEvent> OnItemDoubleClicked;
        public event EventHandler<TreeItemMoveEvent> OnItemMoved;

        private Int32 TreeId = 0;

        private ITreeProvider Provider;
        private List<TreeNode> Roots;
        private List<TreeNode> AllNodes;

        private List<ContextCommandSetup<TreeItemEvent>> ContextMenuCommands = new List<ContextCommandSetup<TreeItemEvent>>();


        /// <summary>
        /// The name of the remote type we wish to instantiate.
        /// </summary>
        private const String RemoteType = "Skugo.StandardPlugins.Tree";


        public Tree(Client client, String displayName) :
            base(RemoteType, client, displayName)
        {
        }

        protected override Object DeserializeDragDropData(Byte[] data)
        {
            var incoming = data.ToBinaryReader();

            var treeId = incoming.ReadInt32();
            var nodeIndex = incoming.ReadInt32();

            if (treeId != this.TreeId)
            {
                return null;
            }

            return this.AllNodes[nodeIndex];
        }

        private void WriteNodes(BinaryWriter outgoing, List<TreeNode> nodes)
        {
            if (nodes == null)
            {
                outgoing.Write(0);
                return;
            }

            outgoing.Write(nodes.Count);

            foreach (var node in nodes)
            {
                outgoing.WriteAsciiString(node.Name);
                outgoing.Write((Byte)node.Icon);

                this.WriteNodes(outgoing, node.Children);
            }
        }

        public void SetSelection(ITreeProvider provider)
        {
            if (this.Provider == provider)
                return;

            if (this.Provider != null)
            {
                this.Provider.TreeChanged -= this.BuildAndSendTree;
            }

            this.Provider = provider;

            if (this.Provider != null)
            {
                this.Provider.TreeChanged += this.BuildAndSendTree;

                this.BuildAndSendTree();
            }
        }

        private void AssignIndicies(List<TreeNode> nodes)
        {
            foreach (var node in nodes)
            {
                this.AllNodes.Add(node);

                if (node.Children != null)
                {
                    this.AssignIndicies(node.Children);
                }
            }
        }

        public void CreateContextCommand(ContextCommandSetup<TreeItemEvent> node)
        {
            this.ContextMenuCommands.Add(node);

            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter outgoing = new BinaryWriter(stream);

            // Write out a the frame buffer message
            outgoing.Write((Byte)MessageType.AddContextMenuItem);

            outgoing.WriteAsciiString(node.Name);

            this.Send(stream.ToArray());
        }

        private void BuildAndSendTree()
        {
            ++this.TreeId;

            this.Roots = this.Provider.GetRoots();

            this.AllNodes = new List<TreeNode>();
            this.AssignIndicies(this.Roots);

            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter outgoing = new BinaryWriter(stream);

            // Write out a the frame buffer message
            outgoing.Write((Byte)MessageType.TreeSnapshot);

            this.WriteNodes(outgoing, this.Roots);

            this.Send(stream.ToArray());
        }

        // Called when packet data is received from our external counterpart
        protected override void OnReceive(Byte[] data)
        {
            var incoming = data.ToBinaryReader();

            var messageType = (MessageType)incoming.ReadByte();

            switch (messageType)
            {
                case MessageType.ItemSelected:
                {
                    Int32 treeId = incoming.ReadInt32();

                    var e = new TreeItemEvent();
                    e.ItemIndex = incoming.ReadInt32();
                    e.Node = this.AllNodes[e.ItemIndex];

                    if (this.TreeId == treeId)
                    {
                        if (this.OnItemSelected != null)
                        {
                            this.OnItemSelected(this, e);
                        }
                    }
                    break;
                }

                case MessageType.ItemDoubleClicked:
                {
                    Int32 treeId = incoming.ReadInt32();

                    if (this.TreeId == treeId)
                    {
                        var e = new TreeItemEvent();
                        e.ItemIndex = incoming.ReadInt32();
                        e.Node = this.AllNodes[e.ItemIndex];

                        if (this.OnItemDoubleClicked != null)
                        {
                            this.OnItemDoubleClicked(this, e);
                        }
                    }
                    break;
                }

                case MessageType.ContextCommandExecuted:
                {
                    Int32 treeId = incoming.ReadInt32();

                    if (this.TreeId == treeId)
                    {
                        var e = new TreeItemEvent();
                        e.ItemIndex = incoming.ReadInt32();
                        e.Node = this.AllNodes[e.ItemIndex];

                        var commandIndex = incoming.ReadInt32();

                        var command = this.ContextMenuCommands[commandIndex];
                        if (command.Handlers != null)
                        {
                            command.Handlers(e);
                        }
                    }
                    break;
                }


                default:
                    throw new Exception("Unhandled message type!");
            }
        }
    }

    public enum RenameMode
    {
        SetByControl,
        CanRename,
        ReadOnly,
    }

    public class TreeNode
    {
        public String Name;
        public Icon Icon;
        public RenameMode Rename;
        public List<TreeNode> Children;
        public Object UserData;
    }

    public delegate void TreeChangedFn();

    public interface ITreeProvider
    {
        event TreeChangedFn TreeChanged;
        List<TreeNode> GetRoots();
    }
}
